﻿using System;
using System.Collections.Generic;
using System.Web;
using System.Text.RegularExpressions;
using System.Text;
using System.Xml;
using LitJson;
using MediaPortal.GUI.Library;

namespace MediaPortal.Plugins.Trailers.Providers {
  public class AppleTrailers : TrailerProviderBase {
    #region Variables

    #region Apple Trailer Trailer Locations

    private const string urlBase = "http://www.apple.com/trailers/";

    private const string xmlNamespace = "http://www.apple.com/itms/";
    private const string urlXMLIndexAll = urlBase + "home/xml/widgets/indexall.xml";
    private const string urlXMLCurrent = urlBase + "home/xml/current.xml";
    private const string urlXMLCurrent480 = urlBase + "home/xml/current_480p.xml";
    private const string urlXMLCurrent720 = urlBase + "home/xml/current_720p.xml";
    private const string urlXMLDetails = "http://www.apple.com/moviesxml/s/";
    private const string urlXMLPoster = "http://images.apple.com/moviesxml/s/";

    private const string urlJsonJustAdded = urlBase + "home/feeds/just_added.json";
    private const string urlJsonExlusive = urlBase + "home/feeds/exclusive.json";
    private const string urlJsonPop = urlBase + "home/feeds/most_pop.json";
    private const string urlJsonHD = urlBase + "home/feeds/just_hd.json";
    private const string urlJsonGenre = urlBase + "home/feeds/genres.json";
    private const string urlJsonStudio = urlBase + "home/feeds/studios.json";

    #endregion

    #endregion

    #region TrailerProviderBase overrides

    public override string ProviderId() {
      return "1";
    }

    public override string ProviderName() {
      return "Apple Movie Trailers";
    }

    public override string ProviderImage() {
      return "";
    }

    public override Index GetIndex() {
      Index rtIndex = new Index();
      rtIndex.Add(new IndexItem("Featured: Just Added", "/just_added", true));
      rtIndex.Add(new IndexItem("Featured: Exclusive", "/exclusive", true));
      rtIndex.Add(new IndexItem("Featured: Just HD", "/just_hd", true));
      rtIndex.Add(new IndexItem("Featured: Most Popular", "/most_pop", true));
      rtIndex.Add(new IndexItem("Featured: Genres", "/featured/genre", true));
      rtIndex.Add(new IndexItem("Featured: Movie Studio", "/featured/studio", true));
      return rtIndex;
    }

    public override Index GetIndex(string key) {
      Log.Info("[MyTrailers][Apple Trailers] Retrieving index: {0}", key);
      return fetchIndex(key);
    }

    public override Trailer GetDetails(string key) {
      Log.Info("[MyTrailers][Apple Trailers] Retrieving details: {0}", key);
      return fetchDetails(key);
    }

    #endregion

    private Index fetchIndex(string key) {
      Index items = new Index();
      string url = string.Empty;

      Log.Info("[MyTrailers][Apple Trailers] Fetching trailers for:  {0}", key);

      switch (key) {
        case "/just_added":
          url = urlJsonJustAdded;
          break;
        case "/exclusive":
          url = urlJsonExlusive;
          break;
        case "/just_hd":
          url = urlJsonHD;
          break;
        case "/most_pop":
          url = urlJsonPop;
          break;
        case "/featured/genre":
          url = urlJsonGenre;
          break;
        case "/featured/studio":
          url = urlJsonStudio;
          break;
        default:
          if (key.StartsWith("/featured/genre")) {
            url = urlJsonGenre;
            break;
          }
          else if (key.StartsWith("/featured/studio")) {
            url = urlJsonStudio;
            break;
          }
          else {
            return items;
          }
      }

      List<string> trailers = getJsonTrailerIndex(url);

      if (key == "/featured/genre") {
        foreach (string trailer in trailers) {
          Trailer t = Trailers[trailer];
          string genre = t.Genres[0];
          IndexItem item = new IndexItem();
          item.Key = key + "/" + genre;
          item.Folder = true;
          item.Label = HttpUtility.HtmlDecode(genre);
          if (!items.Contains(item))
            items.Add(item);
        }
      }
      else if (key == "/featured/studio") {
        foreach (string trailer in trailers) {
          Trailer t = Trailers[trailer];
          string studio = t.Studio;
          IndexItem item = new IndexItem();
          item.Key = key + "/" + studio;
          item.Folder = true;
          item.Label = HttpUtility.HtmlDecode(studio);
          if (!items.Contains(item))
            items.Add(item);
        }
      } 
      else {
        string genre = string.Empty;
        string studio = string.Empty;
        if (key.Contains("/featured/genre/"))
          genre = key.Replace("/featured/genre/", "");

        if (key.Contains("/featured/studio/"))
          studio = key.Replace("/featured/studio/", "");

        foreach (string trailer in trailers) {
          Trailer t = Trailers[trailer];

          if (!String.IsNullOrEmpty(genre) && (t.Genres[0] != genre))
            continue;
          if (!String.IsNullOrEmpty(studio) && (t.Studio != studio))
            continue;

          IndexItem item = new IndexItem();
          item.Key = trailer;
          item.Label = HttpUtility.HtmlDecode(t.Title);
          item.Image = t.Thumb;

          items.Add(item);
        }
      }

      return items;
    }

    private Trailer fetchDetails(string key) {
      Trailer trailer = Trailers.Set(key);

      string url = key.Replace("/trailers/", urlXMLDetails) + "index.xml";
      //Log.Info("[MyTrailers][Apple Trailers] XML details URL: {0}", url); 

      XmlNode Root = GetXml(url);
      if (Root == null) {
        bool failed = true;
        // try second possibility
          try {
              Match altMatch = Regex.Match(trailer.Thumb, @"/images/(.+?)_\d{12}.jpg$");
              if (altMatch.Success) {
                  string altName = altMatch.Groups[1].Value;
                  string altUrl = Regex.Replace(url, @"/([^/]+)/index.xml$", "/" + altName + "/index.xml");
                  Root = GetXml(altUrl);
                  if (Root != null)
                      failed = false;
              }              
          }
          catch (Exception e) {
          }

          if (failed) {
              Log.Error("[MyTrailers][Apple Trailers] No XML Found.");
              return null;
          }
      }

      XmlNamespaceManager nsmgr = new XmlNamespaceManager(Root.OwnerDocument.NameTable);
      nsmgr.AddNamespace("a", xmlNamespace);

      // Poster Image URL
      XmlNode Poster = Root.SelectSingleNode("//a:PictureView/@url[contains(.,'/posters/')]", nsmgr);
      if (Poster.Value.Length > 0) {
        Log.Info("[MyTrailers][Apple Trailers] Added 1 poster.");
        trailer.Poster = Poster.Value;
      }

      // Plot / Description (fuzzy)
      XmlNodeList trlPlot = Root.SelectNodes("//a:VBoxView/a:TextView/a:SetFontStyle", nsmgr);
      if (trlPlot.Count > 1) {
        trailer.Description = HttpUtility.HtmlDecode(trlPlot[2].InnerText);
      }

      // Cast
      XmlNodeList trlCast = Root.SelectNodes("//a:VBoxView/a:TextView[@styleSet='basic10']/a:SetFontStyle", nsmgr);
      foreach (XmlNode actor in trlCast) {
        string newactor = HttpUtility.HtmlDecode(actor.InnerText.Trim());
        if (!trailer.Cast.Contains(newactor))
          trailer.Cast.Add(newactor);
      }

      // Find all the trailer pages for this movie.
      XmlNodeList trlUrls = Root.SelectNodes("//a:GotoURL[@target='main']/@url", nsmgr);
      Dictionary<string, int> validUrls = new Dictionary<string, int>();

      // Match every URL that contains the same path
      // These are most likely the trailer index pages
      string match = url.Replace("/index.xml", "");
      string matchAlt = null;

      try {
          Match altNameMatch = Regex.Match(trailer.Thumb, @"/images/(.+?)_\d{12}.jpg$");
          if (altNameMatch.Success) {
              string altName = altNameMatch.Groups[1].Value;
              matchAlt = Regex.Replace(url, @"/([^/]+)/index.xml$", "/" + altName);
          }
      }
      catch (Exception e) {
          
      }


      foreach (XmlNode trlUrl in trlUrls) {
        string chk = "http://www.apple.com" + trlUrl.Value;
        //if (chk.Contains(match) && !validUrls.ContainsKey(chk))
        if (!validUrls.ContainsKey(chk))
          // add the urls to the valid urls list
          validUrls.Add(chk, 0);
      }

      // Now for each trailer page we found we are 
      // going to get the trailer information
      foreach (string urlDetails in validUrls.Keys) {
        //Log.Info("trailer url: {0}", urlDetails);
        Root = GetXml(urlDetails);
        if (Root == null) {
          Log.Error("[MyTrailers][Apple Trailers] No XML Found for trailer page: {0}", urlDetails);
          continue;
        }

        // Filter the playlist nodes to get the ones we need
        XmlNodeList playlist = Root.FirstChild.SelectNodes("//a:array/a:dict", nsmgr);

        if (playlist.Count > 0) {
          foreach (XmlNode clip in playlist) {
            // Get the node following the previewURL this is the video url of the trailer
            string videourl = clip.SelectSingleNode("a:key[contains(.,'previewURL')]", nsmgr).NextSibling.InnerText;
            // Log.Info("Found video: {0}", videourl);

            // Filter the Ipod format we don't really need it 
            // for display on our our TV do we?
            if (!videourl.Contains(".m4v")) {
              // Get the node following the songName this is the title of the trailer
              string title = clip.SelectSingleNode("a:key[contains(.,'songName')]", nsmgr).NextSibling.InnerText;

              // Get the node following the previewLength this is the duration of the video in seconds
              int duration = int.Parse(clip.SelectSingleNode("a:key[contains(.,'previewLength')]", nsmgr).NextSibling.InnerText);

              // get the release date of this specific trailer
              string date = clip.SelectSingleNode("a:key[contains(.,'releaseDate')]", nsmgr).NextSibling.InnerText;

              string label;
              VideoQuality quality;

              // Parse label and quality from the provided title
              parseAppleVideoTitle(title, out label, out quality);

              // Get existing/new video object by label
              Video video = trailer.Media.Set(label);
              // Add the current quality to the video
              video.Size[quality] = new Uri(videourl);
              // Set the duration of the video
              video.Duration = new TimeSpan(0, 0, duration);

              if (!String.IsNullOrEmpty(date)) {
                DateTime dt;
                if (DateTime.TryParse(date, out dt)) {
                  video.Date = dt;
                }
              }

            }
          }

        }
      }
      Log.Info("[MyTrailers][Apple Trailers] Added {0} trailers.", trailer.Media.Count.ToString());
      return trailer;
    }

    private List<string> getJsonTrailerIndex(string url) {
      List<string> trailers = new List<string>();

      JsonData jsonData = GetJson(url);
      Log.Info("[MyTrailers][Apple Trailers] Found {0} items.", jsonData.Count.ToString());

      foreach (JsonData trailer in jsonData) {
        string key = (string)trailer["location"];

        // no key no movie.. nothing to see here move along
        if (string.IsNullOrEmpty(key))
          continue;

        // If this Trailer exists just add the key to the 
        // list and don't fill the object.
        if (Trailers.ContainsKey(key)) {
          trailers.Add(key);
          continue;
        }

        // Little sanitiy check
        // if we do not have a title we probably don't have a movie
        string title = (string)trailer["title"];
        if (string.IsNullOrEmpty(title))
          continue;

        // Get/Create Trailer object
        Trailer newTrailer = Trailers.Set(key);
        newTrailer.Title = HttpUtility.HtmlDecode(title);
        newTrailer.Studio = (string)trailer["studio"];
        string poster = (string)trailer["poster"];
        newTrailer.Poster = getLargePoster(poster);
        newTrailer.Thumb = poster;
        newTrailer.Rating = (string)trailer["rating"];
        try {
          newTrailer.ReleaseDate = DateTime.Parse((string)trailer["releasedate"]);
        }
        catch { }
        // This is ugly/heavy needs better logic
        // director isn't always available
        try {
          newTrailer.Director = HttpUtility.HtmlDecode((string)trailer["directors"]);
        }
        catch { }

        // List genres
        try {
            foreach (JsonData genre in trailer["genre"]) {
                newTrailer.Genres.Add(genre.ToString());
            }
        }
        catch { }

        // This is ugly needs better logic
        // actors isn't always available
        try {
          foreach (JsonData actor in trailer["actors"]) {
            newTrailer.Cast.Add(HttpUtility.HtmlDecode(actor.ToString()));
          }
        }
        catch { }

        // If we made all the way add the key to the list.
        trailers.Add(key);
      }

      // return the key list
      return trailers;
    }

    #region Apple Trailer Tricks

    private static string getLargePoster(string poster) {
      // simple replacement to get larger poster
      // should probably change this later into a more safe solution.
      //
      // tackle exceptions (sometimes the time differs with one second.. DOH ><)
      // http://images.apple.com/moviesxml/s/wb/posters/rocknrolla_l200808191536.jpg
      // http://images.apple.com/trailers/wb/images/rocknrolla_200808191537.jpg

      string lposter = poster.Replace("http://images.apple.com/trailers/", urlXMLPoster);
      lposter = lposter.Replace("images/", "posters/");
      //lposter = lposter.Replace("_20", "_l20"); // Large
      lposter = lposter.Replace("_20", "_xl20"); // Xtra Large
      return lposter;
    }

    private static void parseAppleVideoTitle(string input, out string title, out VideoQuality quality) {
      // This parses a title formatted as: "Trailer Name (Quality)" 
      // from the playlist and splits it into a title and the quality enum

      string[] parts = input.Split('(');
      if (parts.Length == 2) {
        // this should be the title.
        title = parts[0].Trim();
        // this should be the quality identifier
        string q = parts[1].Replace(")", "").ToLower();
        switch (q) {
          case "small":
            quality = VideoQuality.SMALL;
            break;
          case "medium":
            quality = VideoQuality.MEDIUM;
            break;
          case "large":
            quality = VideoQuality.LARGE;
            break;
          case "hd 480p":
            quality = VideoQuality.HD480;
            break;
          case "hd 720p":
            quality = VideoQuality.HD720;
            break;
          case "hd 1080p":
            quality = VideoQuality.FULLHD;
            break;
          default:
            quality = VideoQuality.UNKNOWN;
            break;
        }
      }
      else {
        title = input;
        quality = VideoQuality.UNKNOWN;
      }
    }

    #endregion

  }
}
